package com.seuic.hnapps.device_implement;

import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;


import com.seuic.android.PinpadEnterPinListener;


import com.seuic.android.TusnInfo;
import com.seuic.hnapps.android.PinModeEnum;
import com.seuic.hnapps.constants.PinpadConstants;
import com.seuic.hnapps.posdsdk.device_interface.PinpadDevice;
import com.seuic.hnapps.util.HexUtil;


import java.util.Arrays;

public class PinpadDeviceImp extends BaseImplement implements PinpadDevice {

    private static PinpadDeviceImp self;

    static PinpadDeviceImp getInstance() {
        if (self == null) {
            synchronized (PinpadDeviceImp.class) {
                if (self == null) {
                    self = new PinpadDeviceImp();
                }
            }
        }
        return self;
    }

    private static final int MAX_KEY_INDEX = 49;

    @Override
    public int downloadProtetKey(int index, String protectKey) {
        Log.d(LOG_TAG, "downloadProtetKey: " + index + ", " + protectKey);
        if (superPinpad == null) {
            setLastError("Pinpad Service Error");
            return -1;
        }
        if (null == protectKey) {
            setLastError("Input Param Error");
            return -1;
        }
        int protect_key_index = getProtectKeyIndex(index);
        if (protect_key_index < 0 || protect_key_index > MAX_KEY_INDEX) {
            Log.e(LOG_TAG, "protect_key_index < 0 || protect_key_index > MAX_KEY_INDEX");
            setLastError("Protect Key Index Error");
            return -2;
        }
        try {
            int result = superPinpad.DownloadPinMK((byte) protect_key_index, HexUtil.hexStringToByteArray(protectKey));
            if (result != 0) {
                Log.e(LOG_TAG, "DownloadPinMK Failed: " + result);
                setLastError("Download Protect Key Error");
                return -3;
            }
            result = superPinpad.DownloadMacMK((byte) protect_key_index, HexUtil.hexStringToByteArray(protectKey));
            if (result != 0) {
                Log.e(LOG_TAG, "DownloadMacMK Failed: " + result);
                setLastError("Download Protect Key Error");
                return -3;
            }
            result = superPinpad.DownloadDesMK((byte) protect_key_index, HexUtil.hexStringToByteArray(protectKey));
            if (result != 0) {
                Log.e(LOG_TAG, "DownloadDesMK Failed: " + result);
                setLastError("Download Protect Key Error");
                return -3;
            }
        } catch (RemoteException e) {
            Log.e(LOG_TAG, "RemoteException: " + e);
            setLastError("Pinpad Service Error");
            return -4;
        }
        return 0;
    }

    private int getProtectKeyIndex(int index) {
        //return 10 + index;
        return 10;
    }

    private int getMainKeyIndex(int index) {
        if (index < 10) {
            return index;
        } else {
            return index + 1;
        }

    }

    @Override
    public int downloadMainkey(int index, String mainKey, String checkValue) {
        Log.d(LOG_TAG, "downloadMainkey: " + index + ", " + mainKey + ", " + checkValue);
        if (superPinpad == null) {
            setLastError("Pinpad Service Error");
            return -1;
        }
        if (mainKey == null || checkValue == null) {
            Log.e(LOG_TAG, "mainKey == null || mainChk == null");
            setLastError("Input Param Error");
            return -1;
        }
        int protect_key_index = getProtectKeyIndex(index);
        if (protect_key_index < 0 || protect_key_index > MAX_KEY_INDEX) {
            Log.e(LOG_TAG, "protect_key_index < 0 || protect_key_index > MAX_KEY_INDEX");
            setLastError("Protect Key Index Error");
            return -2;
        }
        int main_key_index = getMainKeyIndex(index);
        if (main_key_index < 0 || main_key_index > MAX_KEY_INDEX) {
            Log.e(LOG_TAG, "main_key_index < 0 || main_key_index > MAX_KEY_INDEX");
            setLastError("Main Key Index Error");
            return -2;
        }
        try {
            int result = superPinpad.DownloadEncMk((byte) protect_key_index, (byte) main_key_index,
                    HexUtil.hexStringToByteArray(mainKey + checkValue));
            if (0 != result) {
                Log.e(LOG_TAG, "DownloadEncMk Failed: " + result);
                setLastError("Main Key Download Error");
                return -3;
            }
        } catch (RemoteException e) {
            Log.e(LOG_TAG, "RemoteException: " + e);
            setLastError("Pinpad Service Error");
            return -4;
        }
        return 0;
    }

    @Override
    public int downloadWorkKeys(int index, String pinKey, String pinKeyCheckValue, String macKey,
                                String macKeyCheckValue, String tdKey, String tdKeyCheckValue) {

        Log.d(LOG_TAG, "downloadWorkKeys: " + index + ", PinKey:" + pinKey + ", " + pinKeyCheckValue
                + ", MacKey:" + macKey + ", " + macKeyCheckValue
                + ", TDKey: " + tdKey + ", " + tdKeyCheckValue);
        if (superPinpad == null) {
            setLastError("Pinpad Service Error");
            return -1;
        }
        if (pinKey == null || pinKeyCheckValue == null) {
            Log.e(LOG_TAG, "pinKey == null || pinKeyCheckValue == null");
            setLastError("Input Param Error");
            return -1;
        }
        if (macKey == null || macKeyCheckValue == null) {
            Log.e(LOG_TAG, "macKey == null || macKeyCheckValue == null");
            setLastError("Input Param Error");
            return -1;
        }
        if (tdKey == null || tdKeyCheckValue == null) {
            Log.e(LOG_TAG, "tdKey == null || tdKeyCheckValue == null");
            setLastError("Input Param Error");
            return -1;
        }
        int main_key_index = getMainKeyIndex(index);
        if (main_key_index < 0 || main_key_index > MAX_KEY_INDEX) {
            Log.e(LOG_TAG, "main_key_index < 0 || main_key_index > MAX_KEY_INDEX");
            setLastError("MainKey Index Error");
            return -2;
        }
        int work_key_index = main_key_index;
        try {
            int result = pinpad.DownloadPinK((byte) main_key_index, (byte) work_key_index,
                    HexUtil.hexStringToByteArray(pinKey + pinKeyCheckValue));
            if (0 != result) {
                Log.e(LOG_TAG, "DownloadPinK Failed: " + result);
                setLastError("Pin Key Download Error");
                return -3;
            }
            result = pinpad.DownloadMacK((byte) main_key_index, (byte) work_key_index,
                    HexUtil.hexStringToByteArray(macKey + macKeyCheckValue));
            if (0 != result) {
                Log.e(LOG_TAG, "DownloadMacK Failed: " + result);
                setLastError("Mac Key Download Error");
                return -3;
            }
            result = pinpad.DownloadDesK((byte) main_key_index, (byte) work_key_index,
                    HexUtil.hexStringToByteArray(tdKey + tdKeyCheckValue));
            if (0 != result) {
                Log.e(LOG_TAG, "DownloadDesK Failed: " + result);
                setLastError("Track Key Download Error");
                return -3;
            }
        } catch (RemoteException e) {
            Log.e(LOG_TAG, "RemoteException: " + e);
            setLastError("Pinpad Service Error");
            return -4;
        }
        return 0;
    }

    @Override
    public String macKeyEncrypt(int index, String msg) {
        if (pinpad == null) {
            setLastError("Pinpad Service Error");
            return null;
        }
        byte[] msg_byte = HexUtil.hexStringToByteArray(msg);
        byte[] encrypt_byte = new byte[msg_byte.length];
        try {
            int result = pinpad.MacKeyEncrypt((byte) index, msg_byte, encrypt_byte);
        } catch (RemoteException e) {
            Log.e(LOG_TAG, "RemoteException: " + e);
            setLastError("Pinpad Service Error");
            return null;
        }
        return HexUtil.byteToHexString(encrypt_byte);
    }

    @Override
    public String macKeyDecrypt(int index, String msg) {
        if (pinpad == null) {
            setLastError("Pinpad Service Error");
            return null;
        }
        byte[] msg_byte = HexUtil.hexStringToByteArray(msg);
        byte[] encrypt_byte = new byte[msg_byte.length];
        try {
            int result = pinpad.MacKeyDecrypt((byte) index, msg_byte, encrypt_byte);
        } catch (RemoteException e) {
            Log.e(LOG_TAG, "RemoteException: " + e);
            setLastError("Pinpad Service Error");
            return null;
        }
        return HexUtil.byteToHexString(encrypt_byte);
    }

    @Override
    public String trackKeyEncrypt(int index, String msg) {
        if (pinpad == null) {
            setLastError("Pinpad Service Error");
            return null;
        }
        byte[] msg_byte = HexUtil.hexStringToByteArray(msg);
        byte[] encrypt_byte = new byte[msg_byte.length];
        try {
            int result = pinpad.TrackKeyEncrypt((byte) index, msg_byte, encrypt_byte);
        } catch (RemoteException e) {
            Log.e(LOG_TAG, "RemoteException: " + e);
            setLastError("Pinpad Service Error");
            return null;
        }
        return HexUtil.byteToHexString(encrypt_byte);
    }

    @Override
    public String trackKeyDecrypt(int index, String msg) {
        if (pinpad == null) {
            setLastError("Pinpad Service Error");
            return null;
        }
        byte[] msg_byte = HexUtil.hexStringToByteArray(msg);
        byte[] encrypt_byte = new byte[msg_byte.length];
        try {
            int result = pinpad.TrackKeyDecrypt((byte) index, msg_byte, encrypt_byte);
        } catch (RemoteException e) {
            Log.e(LOG_TAG, "RemoteException: " + e);
            setLastError("Pinpad Service Error");
            return null;
        }
        return HexUtil.byteToHexString(encrypt_byte);
    }

    @Override
    public Bundle getTusn(String random) {
        Log.d(LOG_TAG, "getTusn: " + random);
        if (random == null) {
            Log.e(LOG_TAG, "random == null");
            setLastError("Input Param Error");
            return null;
        }
        if (pinpad == null) {
            Log.e(LOG_TAG, "pinpad == null");
            setLastError("Pinpad Service Error");
            return null;
        }
        try {
            TusnInfo tusnInfo = pinpad.GetTusn(HexUtil.hexStringToByteArray(random));

            if (tusnInfo == null) {
                setLastError("Pinpad Service Execute Error");
                return null;
            }
            Bundle bundle = new Bundle();
            bundle.putBoolean(PinpadConstants.TUSN_SUPPORT, tusnInfo.isSupportTusn());
            bundle.putString(PinpadConstants.TUSN, tusnInfo.getTusn());
            bundle.putString(PinpadConstants.TUSN_MAC, tusnInfo.getMac());
            return bundle;
        } catch (RemoteException e) {
            Log.e(LOG_TAG, "RemoteException: " + e);
            return null;
        }
    }

    private PinpadDevice.EnterPinCallback enterPinCallback = null;

    @Override
    public void enterPin(EnterPinCallback enterPinCallback, Bundle param) {
        Log.d(LOG_TAG, "enterPin: " + param);
        if (enterPinCallback == null) {
            Log.e(LOG_TAG, "enterPinCallback == null");
            setLastError("Input Param Error");
            return;
        }
        this.enterPinCallback = enterPinCallback;
        if (param == null) {
            Log.e(LOG_TAG, "param == null");
            enterPinFailed("Input Param Error param == null");
            return;
        }
        String pan = param.getString(PinpadConstants.PAN);
        int pinkey_index = param.getInt(PinpadConstants.PINKEY_INDEX, -1);
        int amount = param.getInt(PinpadConstants.AMOUNT, -1);
        int overtime = param.getInt(PinpadConstants.OVERTIME, -1);
        int[] pin_length_limit = param.getIntArray(PinpadConstants.PIN_LENGTH_LIMITS);
        if (pinkey_index < 0) {
            Log.e(LOG_TAG, "pinkey_index < 0");
            enterPinFailed("Key Index Error");
            return;
        }
        if (pan != null) {
            byte[] pan_in_byte = pan.getBytes();
            byte[] subPanBlock = new byte[12];
            Arrays.fill(subPanBlock, (byte) 0x00);
            int max_len = (pan_in_byte.length - 1 >= 12) ? 12 : (pan_in_byte.length - 1);
            for (int i = 0; i < max_len; i++) {
                subPanBlock[i] = pan_in_byte[pan_in_byte.length - 1 - max_len + i];
            }
            //Log.d( LOG_TAG, "subPanBlock: " + HexUtil.byteToHexString( subPanBlock ) );
            byte[] hexSubPanBlock = HexUtil.asciiNumArrayToHexByteArray(subPanBlock);
            byte[] standard_panblock = new byte[8];
            Arrays.fill(subPanBlock, (byte) 0x00);
            System.arraycopy(hexSubPanBlock, 0, standard_panblock, 2, hexSubPanBlock.length);
            Log.d(LOG_TAG, "standard_panblock: " + HexUtil.byteToHexString(standard_panblock));
            try {
                int result = pinpad.SetPanBlock(standard_panblock);
                if (0 != result) {
                    Log.e(LOG_TAG, "SetPanBlock Failed: " + result);
                    enterPinFailed("Execute Error");
                    return;
                }
            } catch (RemoteException e) {
                Log.e(LOG_TAG, "RemoteException: " + e);
                enterPinFailed("Pinpad Service Error");
                return;
            }
        }
        if (amount > 0) {
            byte[] amount_in_byte = HexUtil.hexStringToByteArray(HexUtil.intToBcdString(amount, 12));
            try {
                int result = pinpad.SetAmount(amount_in_byte);
                if (0 != result) {
                    Log.e(LOG_TAG, "SetAmount Failed: " + result);
                    enterPinFailed("Execute Error");
                    return;
                }
            } catch (RemoteException e) {
                Log.e(LOG_TAG, "RemoteException: " + e);
                enterPinFailed("Pinpad Service Error");
                return;
            }
        }
        if (pin_length_limit != null) {
            byte[] pin_length_limit_in_byte = new byte[pin_length_limit.length];
            for (int i = 0; i < pin_length_limit.length; i++) {
                pin_length_limit_in_byte[i] = (byte) pin_length_limit[i];
            }
            try {
                int result = pinpad.SetPinLen(pin_length_limit_in_byte);
                if (0 != result) {
                    Log.e(LOG_TAG, "SetPinLen Failed: " + result);
                    enterPinFailed("Execute Error");
                    return;
                }
            } catch (RemoteException e) {
                Log.e(LOG_TAG, "RemoteException: " + e);
                enterPinFailed("Pinpad Service Error");
                return;
            }
        }
        int main_key_index = getMainKeyIndex(pinkey_index);
        if (main_key_index < 0 || main_key_index > MAX_KEY_INDEX) {
            Log.e(LOG_TAG, "main_key_index < 0 || main_key_index > MAX_KEY_INDEX");
            enterPinFailed("Key Index Error");
            return;
        }
        overtime = (overtime > 0) ? overtime : 120000;
        byte[] track_key = new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

        try {
            int result = pinpad.EnterPin(new PosdPinpadeEnterPinListener(), (byte) main_key_index,
                    PinModeEnum.ISO9564, (byte) main_key_index, track_key, overtime);
            if (0 != result) {
                Log.e(LOG_TAG, "EnterPin Failed: " + result);
                enterPinFailed("Execute Error");
            }
        } catch (RemoteException e) {
            Log.e(LOG_TAG, "RemoteException: " + e);
            enterPinFailed("Pinpad Service Error");
        }
    }

    private void enterPinFailed(final String error) {
        if (enterPinCallback != null) {
            handler.post(new Runnable() {
                @Override
                public void run() {
                    enterPinCallback.onFail(error);
                }
            });
        }
    }

    private class PosdPinpadeEnterPinListener extends PinpadEnterPinListener.Stub {

        @Override
        public void OnSuccess(final byte[] enc, final int enc_len) throws RemoteException {
            if (enterPinCallback == null) {
                return;
            }
            handler.post(new Runnable() {
                @Override
                public void run() {
                    byte[] data = new byte[enc_len];
                    System.arraycopy(enc, 0, data, 0, enc_len);
                    int i;
                    for (i = 0; i < enc_len; i++) {
                        if (data[i] != (byte) 0xff) {
                            break;
                        }
                    }
                    boolean is_no_pin = false;
                    if (i >= enc_len) {
                        is_no_pin = true;
                    } else {
                        is_no_pin = false;
                    }
                    enterPinCallback.onSuccess(HexUtil.byteToHexString(data), is_no_pin);
                }
            });
        }

        @Override
        public void OnFail(int error_code) throws RemoteException {
            if (error_code == -3) {
                enterPinFailed("Enter Pin Time Over");
            } else if (error_code == -4) {
                enterPinFailed("Enter Pin Canceled");
            } else {
                enterPinFailed("Enter Pin Error: " + error_code);
            }
        }

        @Override
        public void OnInput(final int input_num) throws RemoteException {
            if (enterPinCallback == null) {
                return;
            }
            handler.post(new Runnable() {
                @Override
                public void run() {
                    enterPinCallback.onInput(input_num);
                }
            });
        }
    }
}
